import {
  PerspectiveCamera,
  Vector2,
  WebGLRenderer,
} from 'three';
import { Updater } from './Updater';
import { EffectComposer, ShaderPass } from 'three/examples/jsm/Addons.js';

export class ThreeScene {
  private camera: PerspectiveCamera;
  private renderer: WebGLRenderer;
  private observer: ResizeObserver;
  private update: Updater;
  private composer: EffectComposer;

  private timePass: ShaderPass[] = [];
  private resolutionPass: ShaderPass[] = [];

  constructor(private wrap: HTMLElement) {
    const { width, height } = wrap.getBoundingClientRect();

    this.camera = new PerspectiveCamera(70, width / height, 0.1, 1000);
    this.camera.position.z = 400;

    this.renderer = new WebGLRenderer({ alpha: true, antialias: true });
    this.renderer.setClearAlpha(0);
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(width, height);
    wrap.appendChild(this.renderer.domElement);
    this.observer = new ResizeObserver((entries) => {
      this.onWindowResize(entries[0]);
    });
    this.observer.observe(wrap);
    this.composer = new EffectComposer(this.renderer);

    this.update = new Updater({
      handler: (delta, duration) => {
        this.handleRender(delta, duration);
      }
    });

    this.update.start();
  }

  private handleRender(_delta: number, duration: number) {
    this.timePass.forEach(pass => {
      pass.uniforms.time.value = duration / 1000;
    })
    this.composer.render();
  }

  private onWindowResize(entry: ResizeObserverEntry) {
    const { width, height } = entry.contentRect;

    this.camera.aspect = width / height;

    this.camera.updateProjectionMatrix();
    this.renderer.setSize(width, height);
    this.composer.setSize(width, height);
    this.resolutionPass.forEach(pass => {
      pass.uniforms.resolution.value = new Vector2(width, height);
    })

  }

  addPass(shaders: {
    uniforms?: Record<string, Record<'value', any>>;
    vertexShader: string;
    fragmentShader: string;
  }[]) {
    const { width, height } = this.wrap.getBoundingClientRect();

    shaders.forEach((shader, index) => {
      const pass = new ShaderPass(shader);
      pass.renderToScreen = index === shaders.length - 1;
      if (shader.uniforms?.time !== undefined) {
        this.timePass.push(pass);
      }
      if (shader.uniforms?.resolution !== undefined) {
        pass.uniforms.resolution.value = new Vector2(width, height);
        this.resolutionPass.push(pass);
      }
      this.composer.addPass(pass);
    })
  }

  dispose() {
    this.observer.disconnect();
    this.renderer.dispose();
  }
}
